package island

import (
	"context"
	"fmt"
	"github.com/Unixeno/islands/conf"
	"github.com/Unixeno/islands/island/proxy"
	"github.com/Unixeno/islands/lode"
	"github.com/Unixeno/islands/logging"
	"github.com/Unixeno/islands/stringid"
	"github.com/containerd/go-runc"
	"github.com/opencontainers/runtime-spec/specs-go"
	"golang.org/x/sys/unix"
	"os"
	"os/signal"
	"path/filepath"
	"sync"
)

var logger = logging.GetLogger("island")
var containerRootDir = filepath.Join(conf.GetString("RootPath"), "containers")
var ctx = context.TODO()
var tool *runc.Runc
var toolName string

const (
	diffLength = 12
)

func init() {
	level := logging.GetLevel(conf.GetString("LogLevel"))
	logger.SetLevel(level)

	toolPath := conf.GetString("RuntimeTool")

	// 检查 runtime tool 仅支持 runc crun
	if err := validRuntimeTool(toolPath); err != nil {
		logger.WithField("RuntimeTool", toolPath).WithError(err).Fatal("invalid runtime tool")
	}

	tool = &runc.Runc{
		Command: toolPath,
		Root:    filepath.Join(conf.GetString("RootPath"), "runtime"),
	}

	//初始化execinfo
	execinfo = &ExecInfo{
		ExecLock:      sync.RWMutex{},
		ExecInstances: map[string]*execInstance{},
	}

	// 初始化 processInfo
	processInfo = &ProcessInfo{
		ProcessLock: sync.RWMutex{},
		ProcessMap:  map[int]*Process{},
	}

	// 初始化信号循环
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, unix.SIGCHLD)
	go func() {
		err := signalHandle(ctx, signals)
		if err != nil {
			logger.Debug("signal loop quit")
		}
	}()

	//set child-reaper
	if err := setSubReaper(1); err != nil {
		logger.Error(err)
	}

	// 初始化Overlay Mount Info
	overlayInfo = newOverlayInfo()

	// 创建 containers 目录
	_ = os.MkdirAll(containerRootDir, 0600)

	initDB()
}
func Create(imageID string, baseImageConfig lode.BaseImageConfig) (Factory, error) {
	// 产生一个伪随机数字用作 container id
	logger.Debug("new a basic container")
	id := stringid.GenerateNonCryptoID()
	// 创建 rootfs upper workdir
	if err := overlayCreate(id); err != nil {
		logger.WithError(err).Error("failed to create overlay directories for island")
		return nil, err
	}

	volumes := map[string]*ContainerVolume{}
	for k, _ := range baseImageConfig.Volumes {
		if k == "" {
			continue
		}
		volumes[k] = &ContainerVolume{
			Destination: k,
			RW:          ReadPermission | WritePermission,
		}
	}
	initLayerPath := filepath.Join(containerRootDir, id)
	container := &base{
		ID:     id,
		Bundle: filepath.Join(containerRootDir, id),
		Rootfs: filepath.Join(containerRootDir, id, "rootfs"),
		State:  NewState(),
		Name:   id,
		RuntimeSetting: &runtimeConfig{
			Volumes:    volumes,
			User:       baseImageConfig.User,
			Entrypoint: baseImageConfig.Entrypoint,
			Cmd:        baseImageConfig.Cmd,
			WorkingDir: baseImageConfig.WorkingDir,         // WorkingDir 默认 "/"
			Env:        envSliceToMap(baseImageConfig.Env), //
			Namespace:  baseNamespace(),
		},
		InitSetting: &initConfig{
			DNS:               defaultDns(),
			Hosts:             []*HostEntry{},
			Hostname:          id[:diffLength],
			DNSMountPath:      filepath.Join(initLayerPath, "resolv.conf"),
			HostsMountPath:    filepath.Join(initLayerPath, "hosts"),
			HostnameMountPath: filepath.Join(initLayerPath, "hostname"),
		},
		ImageSetting: &imageConfig{
			ImageID: imageID,
			Path:    "",
		},
		ExecInstances: make([]*execInstance, 0, 20),
	}

	// 新建init进程
	// Todo: cmd
	container.process = NewProcess(ProcessOptions{
		ContainerId: id,
		User:        baseImageConfig.User,
		WorkDir:     baseImageConfig.WorkingDir,
		Command:     baseImageConfig.Cmd,
		Env:         baseImageConfig.Env,
	})
	return container, nil
}

func Open(id string) (Container, error) {
	if item, ok := db.getItem(id); ok {
		return item.container, nil
	}
	logger.WithField("id", id).Error("failed to open container, container doesn't exist")
	return nil, fmt.Errorf("island %q doesn't exist", id)
}

func Edit(id string) (Factory, error) {
	if item, ok := db.getItem(id); ok {
		if checkStatus(id, StatusRunning, StatusExited, StatusRemoving, StatusDead, StatusRestarting) {
			return nil, fmt.Errorf("can not edit %q container", item.container.State.String())
		}
		return item.container, nil
	}
	logger.WithField("id", id).Error("failed to edit container, container doesn't exist")
	return nil, fmt.Errorf("island %q doesn't exist", id)
}

func Remove(id string) error {
	logger.Debug("removing " + id[:diffLength] + "...")
	if item, ok := db.getItem(id); ok {
		if checkStatus(id, StatusRunning) {
			return fmt.Errorf("can not remove %q container", item.container.State.String())
		}
		container := item.container
		err := container.Delete()
		db.delete(id)
		db.save(false, "")
		return err
	}
	logger.WithField("id", id).Error("failed to remove container, container doesn't exist")
	return fmt.Errorf("island %q doesn't exist", id)
}

// Factory interface
func (container *base) Save() (Container, error) {

	saveCheck(container)
	// 创建 init 层
	if err := initLayer(container.ID, container.InitSetting); err != nil {
		logger.WithError(err).Error("failed to make init layer for container")
		return nil, err
	}

	container.RuntimeSetting.Args = containerArgs(container)
	// 生成 spec
	specInfo := &baseSpecInfo{
		rootfs:     container.Rootfs,
		hostname:   container.InitSetting.Hostname,
		args:       container.RuntimeSetting.Args,
		env:        envMapToSlice(container.RuntimeSetting.Env),
		workingDir: container.RuntimeSetting.WorkingDir,
		username:   container.RuntimeSetting.User,
	}
	spec := baseSpec(specInfo)

	addTTY(spec, container)
	// 向 spec 中添加 init 挂载点
	appendInitMount(spec, container.InitSetting)
	// 向 mount 中添加卷
	if err := appendVolume(spec, container); err != nil {
		return nil, err
	}
	// 向 spec 中添加 namespace 设置
	addSpecNamespace(spec, container)
	// 添加 cgroup
	addCgroups(spec, container)
	// 产生 config.json
	if err := saveConfigJson(spec, container); err != nil {
		return nil, err
	}

	if _, ok := db.getItem(container.ID); !ok {
		container.SetCreated()
		db.add(container, filepath.Join(containerRootDir, container.ID, "container.json"))
	}
	db.save(true, container.ID)
	return container, nil
}

func (container *base) SetName(name string) error {
	if name == "" {
		return nil
	}
	var err error
	db.Range(func(k, v interface{}) bool {
		item := v.(*dbItem)
		container := item.container
		if name == container.Name {
			err = fmt.Errorf("%v", "The name exists")
		}
		return true
	})
	if err != nil {
		return err
	}
	container.Name = name
	return nil
}

func (container *base) SetImage(path string) {
	container.ImageSetting.Path = path
}

func (container *base) SetHostname(name string) {
	if name == "" {
		return
	}
	container.InitSetting.Hostname = name
}

func (container *base) SetVolumes(volumes []*ContainerVolume) {
	if volumes == nil || len(volumes) == 0 {
		return
	}
	for _, v := range volumes {
		dest := v.Destination
		if dest == "" {
			continue
		}
		container.RuntimeSetting.Volumes[dest] = v
	}
}

func (container *base) SetEntrypoint(entrypoint []string) {
	if entrypoint == nil || len(entrypoint) == 0 {
		return
	}

	container.RuntimeSetting.Entrypoint = entrypoint
}

func (container *base) SetCmd(cmd []string) {
	if cmd == nil || len(cmd) == 0 {
		return
	}

	container.RuntimeSetting.Cmd = cmd
}

func (container *base) SetWorkingDir(dir string) {
	if dir == "" {
		return
	}

	container.RuntimeSetting.WorkingDir = dir
}

func (container *base) SetUser(user string) {
	if user == "" {
		return
	}
	container.RuntimeSetting.User = user
}

func (container *base) SetEnv(env []string) {
	if env == nil || len(env) == 0 {
		return
	}
	m := envSliceToMap(env)
	for k, v := range m {
		container.RuntimeSetting.Env[k] = v
	}
}

func (container *base) SetHosts(hosts []*HostEntry) {
	if hosts == nil || len(hosts) == 0 {
		return
	}
	container.InitSetting.Hosts = hosts

}

func (container *base) SetDNS(d []string) {
	if d == nil || len(d) == 0 {
		return
	}
	container.InitSetting.DNS = d
}

func (container *base) SetTTY(tty bool) {
	container.RuntimeSetting.TTY = tty
}

func (container *base) SetExposedPorts(info []proxy.ProxyInfo) {
	container.RuntimeSetting.ProxyManager = proxy.GetProxyManager()
	if len(container.RuntimeSetting.Networks) == 0 {
		return
	}
	container.RuntimeSetting.ExposedPorts = info
}

func (container *base) SetNamespace(info []specs.LinuxNamespace) error {
	if info == nil || len(info) == 0 {
		return nil
	}

	for _, entry := range info {
		t := string(entry.Type)
		logger.Debug(t)
		if !validNamespace(t) {
			return fmt.Errorf("invalid namespace %v", entry.Type)
		}
		container.RuntimeSetting.Namespace[t] = entry
	}

	return nil
}

func validNamespace(namespace string) bool {
	namespaces := []string{"pid", "network", "ipc", "uts", "mount", "user", "cgroup"}
	for _, ns := range namespaces {
		if ns == namespace {
			return true
		}
	}
	return false
}
